package org.geom.curve.bezier;

import java.util.ArrayList;

import org.geom.Line2D;
import org.geom.Maths;
import org.geom.Point2D;
import org.geom.Point3D;
import org.geom.Vector3D;
import org.geom.curve.Curve;

public class Bezier extends Curve {

	protected ArrayList<Point3D> controlPoints;
	protected ArrayList<Line2D> controlSegments;

//	@Override
//	public ArrayList<Point3D> getPoints() {
//
//		System.out.println("control points");
//		System.out.println(controlPoints.toString());
//		
//		System.out.println("points");
//		System.out.println(this.points);
//
//		System.out.println(this.getArc());
//		
//		return super.getPoints();
//	}
	
	
	//---------------------------------------------------------------------- contructors
	public Bezier(int n){
		ArrayList<Point3D> pts = new ArrayList<Point3D>();
		
//		pts.add(new Point3D(0,0,0));
//		pts.add(new Point3D(10,0,0));
//		pts.add(new Point3D(10,10,0));
//		pts.add(new Point3D(50,0,0));
//		pts.add(new Point3D(60,-70,40));
//		pts.add(new Point3D(-80,-80,50));
//		pts.add(new Point3D(0,0,0));
		
		for(int i=0; i<=n; i++) {
			float x = Maths.random(50);
			float y = Maths.random(50);
			float z = Maths.random(50);
			pts.add(new Point3D(i*30+x,i*30+y,i*30+z));
		}
		
		setControlPoints(pts);
	}

	public Bezier(ArrayList<Point3D> points) {
		this.setControlPoints(points);
	}

	//---------------------------------------------------------------------- privates 

	private Point3D deltaPoint(int k, int i) {
		
		/*
		 * To express higher derivative concisely, we shall use finite difference. 
		 * Let us define Di0 to be control point Pi for 0 <= i <= n.
		 * Then, we define the first level difference Di1 to be the difference of the previous level.
		 * The second level difference is defined to be the difference of the first level points.
		 * Repeating this procedure recursively, we can define the k-th level difference as follows. 
		 * 
		 * */
		Point3D p = new Point3D();
		
		if(k==0) {
			return controlPoints.get(i);
		}
		
		if(k==1 && i==0) {
			p.x = controlPoints.get(1).x - controlPoints.get(0).x;
			p.y = controlPoints.get(1).y - controlPoints.get(0).y;
			p.z = controlPoints.get(1).z - controlPoints.get(0).z;
			return p;
		}
		
		if(k==1 && i==1) {
			p.x = controlPoints.get(2).x - controlPoints.get(1).x;
			p.y = controlPoints.get(2).y - controlPoints.get(1).y;
			p.z = controlPoints.get(2).z - controlPoints.get(1).z;
			return p;
		}
		
		Point3D p1, p2;
		p1 = deltaPoint(k-1, i+1);
		p2 = deltaPoint(k-1, i);
		
		p.x = p1.x - p2.x;
		p.y = p1.y - p2.y;
		p.z = p1.z - p2.z;
		
		return p;
	}

	//-------------------------------------------------- abstract implementation
	
	public Point3D sample(float t) {
		float b, 
		x = 0, 
		y = 0, 
		z = 0;

		for (int i=0; i<=degree(); i++) {
			b = Bernstein.compute(t, i, degree());
			x += (controlPoints.get(i).x)*b;
			y += (controlPoints.get(i).y)*b;
			z += (controlPoints.get(i).z)*b;
		}
		
		//System.out.println("Bezier > sample > p:"+ new Point3D(x,y,z));
		
		return new Point3D(x,y,z);
		
	}

	
	protected Vector3D derivateI(float t)  {  //TODO : test
		/*	
		 *  First derivate in Bezier with casteljeau algorithm
		 *  the first curve C1(u) is defined by control points P1, P2, ..., Pn
		 *  the second curve C2(u) is defined by control points P0, P1, ..., Pn-1
		 *  C'(u) = n(C1(u) - c2(u))
		 *  
		 */
		 
		float 
		x = 0, 
		y = 0, 
		z = 0, 
		b;
		
		Point3D p;
		
		int degree = degree();
		
		for (int i=0; i<degree; i++) {
			b = Bernstein.compute(t, i, degree-1);
			
			p = deltaPoint(1, i);
			
			x += p.x * b;
			y += p.y * b;
			z += p.z * b;
		}
		return new Vector3D(degree*x, degree*y, degree*z);		
	}
	
	protected Vector3D derivateII(float t) { //TODO : test
		
		int degree = degree();
		
		float 
		x = 0, 
		y = 0, 
		z = 0, 
		b,
		k = degree * (degree-1);
		
		Point3D p;
		
		for (int i=0; i<degree-2; i++) {
			b = Bernstein.compute(t, i, degree-2);
			
			p = deltaPoint(2, i);
			
			x += b * p.x;
			y += b * p.y;
			z += b * p.z;
		}

		return new Vector3D(k*x, k*y, k*z);
	}


	//---------------------------------------------------------------------- getter && setter

	public void setControlPoints(ArrayList<Point3D> pts) {
		controlPoints = pts;
		getContructionSegments();
	}
	public ArrayList<Point3D> getControlPoints() {
		return controlPoints;
	}

	public int degree() {
		return controlPoints.size()-1;
	}
	
//	public float getT() {
//		return length/length;
//	}

	//-------------------------------------------------- for construction segments

	public ArrayList<Line2D> getControlSegments() {
		return controlSegments;
	}

	private void getContructionSegments() {
		Point3D _point,_ppoint;
		Line2D _s;
		controlSegments = new ArrayList<Line2D>();
		_ppoint = (Point3D)controlPoints.get(0);
		for(int i=1;i<controlPoints.size(); i++) {
			_point = (Point3D)controlPoints.get(i);
			_s = new Line2D(_ppoint, _point);
			controlSegments.add(_s);
			_ppoint = _point;
		}
	}

	public ArrayList<ArrayList<Line2D>> getAllConstructionLine(float t) {
		/*
		 * Return all middle line between controlPoints and 
		 * all line between the point in those line (in proportion of t)
		 * and so over,
		 * depending on degree of the Bezier
		 * ( = the number of original control point)
		 * 
		 * */
		Point2D p1, p2;
		ArrayList<ArrayList<Line2D>> allLine = new ArrayList<ArrayList<Line2D>>();

		allLine.add( getControlSegments() );

		for(int i=1; i<degree(); i++) {
			ArrayList<Line2D> temp = allLine.get(i-1); //array line2d 
			ArrayList<Line2D> newlist = new ArrayList<Line2D>(); //array line2d

			Line2D s;
			p2 = ((Line2D)temp.get(0)).inbetween(t);
			//boolean b = true;
			for(int j=1; j<temp.size(); j++) {
				p1 = ((Line2D)temp.get(j)).inbetween(t);
				s = new Line2D(p2, p1);
				newlist.add(s);
				p2 = p1;
			}
			allLine.add(newlist);
		}

		return allLine;

	}


	@Override
	public float getArc() {
		return 1/((float)length-1);
	}

	
}
